package edu.buaa.act.anonymous;

import edu.buaa.act.helper.Utils;
import edu.buaa.act.model.GPSPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * Created by T7810 on 2017/4/22.
 * Linear interpolation
 * Normalize timestamp to time slot
 */
public class LinearInterpolation
{
    private Logger log = Utils.getDebugLogger();
    /**
     * all traj in parsedData has the same length/size. e.g. parsedData.get(i).size()==parsedData.get(j).size(), 0<i,j<N
     */
    private List<List<GPSPoint>> parsedData = new ArrayList<>();
    private Map<Integer, Long> timeSlot2Stamp = new HashMap<>();
    private Map<Long, Integer> timeStamp2Slot = new HashMap<>();

    private long startTime;
    private long endTime;
    private int timeInterval;

    /**
     *
     * @param separatedRawData
     * @param startTime
     * @param endTime
     * @param timeIntervalMillisecond in milliseconds
     */
    public LinearInterpolation(List<List<GPSPoint>> separatedRawData, long startTime, long endTime, int timeIntervalMillisecond)
    {
        this.startTime = startTime;
        this.endTime = endTime;
        this.timeInterval = timeIntervalMillisecond;
        for(List<GPSPoint> traj:separatedRawData)
        {

            if(traj.get(0).getTimestamp() <= startTime &&
                    endTime <= traj.get(traj.size()-1).getTimestamp())
            {
                List<GPSPoint> normalizedTraj = new ArrayList<>();
                int i=0;
                for(long t=startTime; t<=endTime; t+=timeInterval){
                    while(traj.get(i).getTimestamp()<t) i++;
                    if(traj.get(i).getTimestamp()==t){
                        normalizedTraj.add(traj.get(i));
                    }else{ // time > t
                        GPSPoint p = traj.get(i);
                        GPSPoint pre = traj.get(i - 1);
                        double percent = ((double)(t - pre.getTimestamp())) /
                                (p.getTimestamp() - pre.getTimestamp());
                        double newLat = pre.getLatitude() + percent * (p.getLatitude() - pre.getLatitude());
                        double newLon = pre.getLongitude() + percent * (p.getLongitude() - pre.getLongitude());
                        GPSPoint newPoint = new GPSPoint(p.getCarNo(), t, newLon, newLat, -1, -1);
                        newPoint.setCarID(p.getCarID());
                        normalizedTraj.add(newPoint);
                    }
                }
//                long t=startTime;
//                for(int j=0; j<traj.size() && t<=endTime; j++)
//                {
//                    GPSPoint p = traj.get(j);
//                    if(p.getTimestamp()==t)
//                    {
//                        normalizedTraj.add(p);
//                        t+=timeInterval;
//                    }
//                    else if(p.getTimestamp()>t)
//                    {
//                        GPSPoint pre = traj.get(j - 1);
//                        double percent = ((double) (t - pre.getTimestamp())) /
//                                (p.getTimestamp() - pre.getTimestamp());
//                        double newLat = pre.getLatitude() + percent * (p.getLatitude() - pre.getLatitude());
//                        double newLon = pre.getLongitude() + percent * (p.getLongitude() - pre.getLongitude());
//                        normalizedTraj.add(new GPSPoint(p.getCarNo(), t, newLat, newLon, -1, -1));
//                        t += timeInterval;
//                    }
//                }
                parsedData.add(normalizedTraj);
            }else{
                throw new RuntimeException("traj not cover whole time interval!");
            }

        }
//        printInfo();
        setTimeSlot(this.parsedData);
    }

    public int getTrajGPSPointCount(){
        return (int) ((this.endTime - this.startTime)/this.timeInterval+1);
    }

    private void setTimeSlot(List<List<GPSPoint>> parsedData)
    {
        boolean firstLoop=true;
        for(List<GPSPoint> traj:parsedData)
        {
            int slot=0;
            for(GPSPoint p : traj)
            {
                p.setTimeSlot(slot);
                if(firstLoop){
                    timeSlot2Stamp.put(slot, p.getTimestamp());
                    timeStamp2Slot.put(p.getTimestamp(), slot);
                }else{
                    Long stamp = timeSlot2Stamp.get(slot);
                    if(stamp==null || stamp!=p.getTimestamp()){
                        throw new RuntimeException("timestamp not match!");
                    }
                }
                slot++;
            }
            firstLoop = false;
            if(slot!=getTrajGPSPointCount()){
                throw new RuntimeException("size not equal to getTrajGPSPointCount()="+getTrajGPSPointCount()+" vs "+slot+"!");
            }
        }
    }

    public Long timeSlot2timeStamp(int timeSlot){
        return timeSlot2Stamp.get(timeSlot);
    }

    public Integer timeStamp2timeSlot(long timestamp){
        return timeStamp2Slot.get(timestamp);
    }

    public void toFile(File output) throws IOException
    {
        log.debug("write training data to {}", output.getAbsolutePath());

        BufferedWriter w = new BufferedWriter(new FileWriter(output));
        for(List<GPSPoint> traj:parsedData)
        {
            for (GPSPoint p : traj) {
                w.write(p.toStringShort('\t'));
                w.write('\n');
            }
        }
        w.close();
    }

    public List<List<GPSPoint>> getParsedData(){
        return parsedData;
    }

    public void printInfo()
    {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        df.setTimeZone(TimeZone.getTimeZone("GMT+8:00"));
        log.debug("carID  gpsPointCount  startTimestamp  endTimestamp  timeInterval(seconds)");
        for(List<GPSPoint> traj : parsedData)
        {
            log.debug("{} {} {} {} {}",
                    traj.get(0).getCarID(),
                    traj.size(),
                    df.format(traj.get(0).getTimestamp()),
                    df.format(traj.get(traj.size()-1).getTimestamp()),
                    (traj.get(traj.size()-1).getTimestamp()- traj.get(0).getTimestamp())/1000
            );
        }
    }
}
